06.1 精通自定义 View 之 Paint 基本使用——硬件加速

返回自定义 View 目录

参考文档:硬件加速

6.1.1 本质和原理

1. 概念

所谓硬件加速,指的是把某些计算工作交给专门的硬件来做,而不是和普通的计算工作一样交给 CPU 来处理。这样不仅减轻了 CPU 的压力,而且由于有了「专人」的处理,这份计算工作的速度也被加快了。这就是「硬件加速」。

而对于 Android 来说,硬件加速有它专属的意思:在 Android 里,硬件加速专指把 View 中绘制的计算工作交给 GPU 来处理。进一步地再明确一下,这个「绘制的计算工作」指的就是把绘制方法中的那些 Canvas.drawXXX() 变成实际的像素这件事。

2. 原理

在硬件加速关闭的时候,Canvas 绘制的工作方式是:把要绘制的内容写进一个 Bitmap,然后在之后的渲染过程中,这个 Bitmap 的像素内容被直接用于渲染到屏幕。这种绘制方式的主要计算工作在于把绘制操作转换为像素的过程(例如由一句 Canvas.drawCircle() 来获得一个具体的圆的像素信息),这个过程的计算是由 CPU 来完成的。大致就像这样:

而开启硬件加速后,Canvas 的工作方式改变了:它把绘制的内容转为 GPU 的操作保存下来,然后交给 GPU 来完成显示工作。大致过程:

从上图可以看出,开启硬件加速后,绘制的计算工作有 CPU 交给 GPU,不过这怎么就能起到加速作用,让绘制变快了呢?

  • 本来 CPU 的工作,分摊一部分给 GPU,自然可以提高效率;
  • 相对于 CPU 来说,GPU 自身的设计本来就对于很多常见类型内容的计算 (例如简单的圆形、方形) 具有优势;
  • 由于绘制流程的不同;硬件加速在界面内容发生重绘的时候绘制流程可以得到优化,避免一些重复操作,从而大幅提升绘制效率。

关于第三点,它的原理大致说一下:

关闭硬件加速时,绘制内容会被 CPU 转为实际的像素,然后直接渲染到屏幕,具体来说,这个「实际的像素」,是由 bitmap 承载的,在界面的某个 View 由于内容发生改变而调用 invalidat() 方法时,如果没有开启硬件加速,为了正确计算 bitmap 的像素,这个 View 的父 View、父 View 的父 View 乃至一直向上知道最顶级的 View,以及所有和它相交的 View,都需要被调用 invalidate() 来重绘,一个 View 的改变使得大半个界面甚至整个界面重绘一遍,这个工作量是非常大的。

而在开启硬件加速时,绘制的内容会被转换成 GPU 的操作保存下来 (承载的形式成为 displaylist,对应的类也叫作 DisplayList),再转交给 GPU。由于所有绘制的内容都没有变成最终的像素,所以它们之间是相互独立的,那么在界面内容发生改变时,只需把发生了改变的 View 调用 invalidate() 方法以更新它所对应的 GPU 就好,至于它的父 View 和兄弟 View,只需要保持原样,那么这个工作量就很小了。

正是由于上面的原因,硬件加速不仅是由于 GPU 的引入提高效率,而且因为绘制机制的改变,而极大的提高了界面内容改变时的刷新效率。

总结:用了 GPU,绘制更快;绘制机制的改变,导致界面内容改变时的刷新效率极大提高。

6.1.2 在 Android 中的限制

可事实就是,硬件加速不止有好处,也有限制:收到 GPU 绘制方式的限制,Canvas 的有些方法在硬件加速开启时会失效或者无法正常工作,比如:开启硬件加速,clipPath() 在 API 18 及以上系统中才有效,具体的 API 限制和 API 版本的关系如下图:

所以,如果你对自定义控件有自定义绘制的内容,最好参照一下表格,确保你的绘制操作可以正确地在所有用户手机中正常显示,而不是只在你最新 Android 系统的 Nexus 或 Pixel 里测试一遍没问题就发布。那就小心被祭天了。

6.1.3 禁用 GPU 硬件加速的方法

1)在 AndroidManifest.xml 文件中为 application 标签添加如下属性,即可为整个应用程序开启/关闭硬件加速。

1
<application android:hardwareAccelerated="true" ...>

2)在 AndroidManifest.xml 文件中为 activity 标签下使用 hardwareAccelerated 属性开启/关闭硬件加速。

1
<activity android:hardwareAccelerated="false" ...>

3)在 Window 层级上使用如下代码开启硬件加速(Window 层级不支持关闭硬件加速):

1
2
getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);

4)在 View 层级上使用如下代码关闭硬件加速(在 View 层级上不支持开启硬件加速)

1
setLayerType(LAYER_TYPE_SOFTWARE,null);

或者在layout xml中使用

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layerType="software" >
</LinearLayout>

事实上,view.setLayerType(LAYER_TYPE_SOFTWARE, null) 这个方法的作用并不是关闭硬件加速,只是当它的参数为 LAYER_TYPE_SOFTWARE 的时候,可以顺便把硬件加速关掉而已;并且除了这个方法外,Android 并没有提供专门的 View 级别的硬件加速开关,所以它就顺便成了一个开关硬件加速的方法。

  • 参数为 LAYER_TYPE_SOFTWARE 时,使用软件来绘制 View Layer,绘制到一个 Bitmap,并顺便关闭硬件加速;
  • 参数为 LAYER_TYPE_HARDWARE 时,使用 GPU 来绘制 View Layer,绘制到一个 OpenGL texture (如果硬件加速关闭,那么行为和 LAYER_TYPE_SOFTWARE 一致);
  • 参数为 LAYER_TYPE_NONE 时,关闭 View Layer。